package com.atguigu.gmall.index.service.impl;

import com.atguigu.gmall.common.bean.ResponseVo;
import com.atguigu.gmall.index.feign.PmsClient;
import com.atguigu.gmall.index.service.IndexService;
import com.atguigu.gmall.pms.entity.CategoryEntity;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @Author zhaoshuo
 * @ClassName IndexServiceImpl
 * @Description
 */
@Service
public class IndexServiceImpl implements IndexService {
    @Autowired
    private PmsClient pmsClient;
    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public List<CategoryEntity> queryLevelOneCategories() {
        ResponseVo<List<CategoryEntity>> responseVo = pmsClient.parent("0");
        return responseVo.getData();
    }

    @Override
    public List<CategoryEntity> levelTwoAndSubsCates(String cid) {
        String key = "idx:cache:cates:" + cid;
        Object obj = redisTemplate.opsForValue().get(key);
        if (obj != null) {
            return (List<CategoryEntity>) obj;
        }
        ResponseVo<List<CategoryEntity>> responseVo = pmsClient.lv2AndSubsCates(cid);
        if (CollectionUtils.isEmpty(responseVo.getData())) {
            redisTemplate.opsForValue()
                    .set(key, responseVo.getData(),
                            new Random().nextInt(500), TimeUnit.SECONDS);
        } else {
            redisTemplate.opsForValue()
                    .set(key, responseVo.getData(),
                            1800 + new Random().nextInt(200), TimeUnit.SECONDS);
        }
        return responseVo.getData();
    }

    public void check(String lockKey, String uuid) {
        Boolean lock = tryLock(lockKey, uuid, 30L);
        if (lock) {

            System.out.println("环境检查!!!");
            releaseLock(lockKey, uuid);
        } else {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            check(lockKey, uuid);
        }

    }

    @Override
    public void testLock() throws InterruptedException {
        String uuid = UUID.randomUUID().toString();
        Boolean flag = tryLock("lock", uuid, 30L);
        if (flag) {
            check("lock", uuid);
            int num = Integer.parseInt(redisTemplate.opsForValue().get("num").toString());
            num++;
            redisTemplate.opsForValue().set("num", num);
            releaseLock("lock", uuid);
        } else {
            Thread.sleep(50);
            testLock();
        }
    }

    public void releaseLock(String lock, String uuid) {
        String script = "if redis.call('hexists' ,KEYS[1] , KEYS[2])==0 " +
                " then " +
                "   return 0 " +
                " elseif tonumber(redis.call('hget' , KEYS[1] , KEYS[2])) == 1 " +
                "  then " +
                "    redis.call('del' ,KEYS[1]) return 1 " +
                " else " +
                "    redis.call('hincrby' , KEYS[1] , KEYS[2], -1) return 2 end";
        System.out.println(script);
        redisTemplate.execute(new DefaultRedisScript(script, Long.class), Arrays.asList(lock, uuid));
    }

    public Boolean tryLock(String lock, String uuid, Long expireTime) {
        String script = "if redis.call('exists' , KEYS[1])==0 " +
                " then " +
                "   redis.call('hset' , KEYS[1] , KEYS[2] , 1) " +
                "   redis.call('expire' , KEYS[1] ,ARGV[1]) " +
                "   return 1 " +
                " elseif redis.call('hexists' , KEYS[1] , KEYS[2])==1 " +
                " then " +
                "   redis.call('hincrby' , KEYS[1] , KEYS[2] , 1) return 2 " +
                " else return 0  end ";

        Long flag = (Long) redisTemplate.execute(new DefaultRedisScript(script, Long.class),
                Arrays.asList(lock, uuid), expireTime);
        if (flag == 1) {
            renewKey(expireTime, lock, uuid);
        }
        return flag != 0;
    }

    ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);

    public void renewKey(Long expireTime, String lock, String uuid) {
        pool.schedule(() -> {
            String script = "if redis.call('hexists' , KEYS[1] , KEYS[2])==1 " +
                    " then redis.call('expire' ,KEYS[1] ,ARGV[1]) return 1 " +
                    " else return 0 end";
            while ((Boolean) redisTemplate.execute(new DefaultRedisScript(script, Boolean.class),
                    Arrays.asList(lock, uuid), expireTime)) {
                try {
                    Thread.sleep(expireTime * 2000 / 3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            ;
        }, expireTime * 2000 / 3, TimeUnit.MILLISECONDS);
    }
}